#include "Visualisation.h"
#include "GfxEntity.h"
#include "GfxEntityModel.h"
#include "GfxEntitySkybox.h"
#include "GfxEntityTerrain.h"
#include "Camera.h"
#include <assert.h>

CVisualisation* CVisualisation::instance=0;

CVisualisation::CVisualisation(void):m_d3dObject(0), m_d3dDevice(0), m_modeWireframe(false), m_font(0), m_sprite(0)
{
	m_camera= new CCamera();
}

CVisualisation::~CVisualisation(void)
{
	for(vector<CGfxEntity*>::iterator i=m_vectorGfxEntity.begin(); i!=m_vectorGfxEntity.end(); i++)
	{
		delete (*i);
	}
	for(vector<LPDIRECT3DTEXTURE9>::iterator i=m_vectorTexture.begin(); i!=m_vectorTexture.end(); i++)
	{
		(*i)->Release();
	}
	if(m_sprite!=0)
		m_sprite->Release();
	if(m_font!=0)
		m_font->Release();
	if(m_d3dDevice!=0)
		m_d3dDevice->Release();
	if(m_d3dObject)
		m_d3dObject->Release();
	if(m_camera!=0)
		delete m_camera;
}

CVisualisation* CVisualisation::getInstance(void)
{
	if(instance==0)
		instance=new CVisualisation();

	return instance;
}

void CVisualisation::close()
{
	delete instance;
	instance=0;
}

bool CVisualisation::createWindow(int width, int height, const WNDCLASSEX* windowClass, HINSTANCE hInstance, int nCmdShow)
{
	assert(width>0);
	assert(height>0);

	//register the window class
	RegisterClassEx(windowClass);

	//create the window
	m_hWnd=CreateWindow("MyWindowClass", "DIRECTX | GAMES SOFTWARE DEVELOPMENT 2", WS_OVERLAPPEDWINDOW, 
		CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL);
	if(m_hWnd==0)
		return false;
	else
	{
		//assign width and height
		m_width=width;
		m_height=height;
		//show and update the window
		ShowWindow(m_hWnd, nCmdShow);
		UpdateWindow(m_hWnd);
		return true;
	}
}

bool CVisualisation::initDirectX()
{
	//create the d3d main object
	m_d3dObject=Direct3DCreate9(D3D_SDK_VERSION);
	D3DPRESENT_PARAMETERS d3dPresParam;
	ZeroMemory(&d3dPresParam, sizeof(d3dPresParam));
	d3dPresParam.Windowed=true;
	d3dPresParam.SwapEffect=D3DSWAPEFFECT_DISCARD;
	d3dPresParam.BackBufferFormat=D3DFMT_UNKNOWN;
	d3dPresParam.PresentationInterval=D3DPRESENT_INTERVAL_ONE;
	d3dPresParam.EnableAutoDepthStencil=true;
	d3dPresParam.AutoDepthStencilFormat=D3DFMT_D16;

	//create the device
	HRESULT hr=m_d3dObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, 
										D3DCREATE_HARDWARE_VERTEXPROCESSING,&d3dPresParam, &m_d3dDevice);
	if(FAILED(hr))
	{
		hr=m_d3dObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, 
									D3DCREATE_MIXED_VERTEXPROCESSING, &d3dPresParam, &m_d3dDevice);
		if(FAILED(hr))
		{
			hr=m_d3dObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, 
										D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dPresParam, &m_d3dDevice); 
			if(FAILED(hr))
			{
				return false;
			}
		}	
	}

	//set the projection matrix
	float aspect=(800/600);
	D3DXMatrixPerspectiveFovLH(&projMatrix, D3DX_PI/4, aspect, 1, 1000);
	m_d3dDevice->SetTransform(D3DTS_PROJECTION, &projMatrix);

	//set the light as a directional light
	m_d3dDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(255, 255, 255));

	//turn on the Z buffer
	m_d3dDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

	//turn the light on
	m_d3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);

	//turn on the bilinear filtering
	m_d3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
	m_d3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
	m_d3dDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR );

	// Clamp texture co-ordinates to get rid of horrible seam
	m_d3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
	m_d3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

	//init the sprite variable
	hr=D3DXCreateSprite(m_d3dDevice, &m_sprite);
	if(FAILED(hr))
		return false;

	return true;
}

bool CVisualisation::loadModel(int* indexEntity, string filePath)
{
	CGfxEntityModel* newEntity = new CGfxEntityModel();
	//CGfxEntity* pntNewEntity=newEntity;
	if(newEntity->load(filePath))
	{
		(*indexEntity)=(int)m_vectorGfxEntity.size();
		m_vectorGfxEntity.push_back(newEntity);
		return true;
	}
	else
		return false;	
}

bool CVisualisation::loadSkyBox(int* indexEntity, string adressModel)
{
	CGfxEntitySkybox* newEntity = new CGfxEntitySkybox();
	//CGfxEntity* pntNewEntity=newEntity;
	
	if(newEntity->load(adressModel))
	{
		(*indexEntity)=(int)m_vectorGfxEntity.size();
		m_vectorGfxEntity.push_back(newEntity);
		return true;
	}
	else
		return false;
}

bool CVisualisation::loadTerrain(int* indexEntity, int numCellsWide, int numCellsHigh, D3DXVECTOR3 minBound, D3DXVECTOR3 maxBound)
{
	CGfxEntityTerrain* newTerrain = new CGfxEntityTerrain(numCellsWide, numCellsHigh, minBound, maxBound);
	newTerrain->createTerrain();
	(*indexEntity)=(int)m_vectorGfxEntity.size();
	m_vectorGfxEntity.push_back(newTerrain);
	return true;
}

bool CVisualisation::createFont()
{
	HRESULT hr=D3DXCreateFont(m_d3dDevice, 50, 20, FW_BOLD, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 
		DEFAULT_PITCH|FF_DONTCARE, TEXT("Charlemagne Std"), &m_font);

	if(FAILED(hr))
		return false;

	return true;
}

bool CVisualisation::loadTexture(int* indexEntity, string addressTexture)
{
	LPDIRECT3DTEXTURE9 newTexture;
	HRESULT hr=D3DXCreateTextureFromFile(m_d3dDevice, addressTexture.c_str(), &newTexture);
	if(FAILED(hr))
		return false;

	(*indexEntity)=(int)m_vectorTexture.size();
	m_vectorTexture.push_back(newTexture);

	return true;
}

void CVisualisation::createDirectionalLight(D3DXVECTOR3* direction, DWORD index)
{
	// Fill in a light structure defining our light
	 D3DLIGHT9 light;
	 ZeroMemory( &light, sizeof(D3DLIGHT9) );
	 light.Type    = D3DLIGHT_DIRECTIONAL;
	 light.Diffuse.r = 1.0f;
	 light.Diffuse.g = 1.0f;
	 light.Diffuse.b = 1.0f;
	 light.Diffuse.a = 1.0f;
	 light.Range   = 1000.0f;

	 // normalize the direction
	 D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, direction );

	 // Tell the device about the light and turn it on
	 m_d3dDevice->SetLight( index, &light );
	 m_d3dDevice->LightEnable( index, TRUE );
}

void CVisualisation::setWireframeMode(bool set)
{
   if (set)
     m_d3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
   else
     m_d3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

   m_modeWireframe=set;
}

HWND CVisualisation::getWindowHandle(void)const
{
	return m_hWnd;
}

LPDIRECT3DDEVICE9 CVisualisation::getDevice()const
{
	return m_d3dDevice;
}

CCamera* CVisualisation::getCamera()const
{
	return m_camera;
}

D3DXVECTOR3 CVisualisation::getMinBoundTerrain(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return terrain->getMinBound();
}

D3DXVECTOR3 CVisualisation::getMaxBoundTerrain(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return terrain->getMaxBound();
}

float CVisualisation::getCellWidthTerrain(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return (terrain->getMaxBound().x-terrain->getMinBound().x)/terrain->getNumCellWidth();
}

float CVisualisation::getCellHeightTerrain(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return (terrain->getMaxBound().z-terrain->getMinBound().z)/terrain->getNumCellHigh();
}

int CVisualisation::getNumCellWide(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return terrain->getNumCellWidth();
}

int CVisualisation::getNumCellHigh(int indexTerrain)const
{
	assert(indexTerrain>=0);

	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return terrain->getNumCellHigh();
}

D3DXVECTOR3 CVisualisation::getVertexTerrain(int indexTerrain, int indexVertex)const
{
	assert(indexTerrain>=0);
	assert(indexVertex>=0);
	CGfxEntityTerrain* terrain=(CGfxEntityTerrain*)m_vectorGfxEntity[indexTerrain];
	return terrain->getVertex(indexVertex);
}

LPD3DXMESH CVisualisation::getMesh(int index)const
{
	assert(index>=0);

	CGfxEntityModel* model=(CGfxEntityModel*)m_vectorGfxEntity[index];
	return model->getMesh();
}

bool CVisualisation::getModeWireframe()const
{
	return m_modeWireframe;
}

int CVisualisation::getWindowWidth()const
{
	return m_width;
}

int CVisualisation::getWindowHeight()const
{
	return m_height;
}

void CVisualisation::displayModel(int gfxIndex, D3DXVECTOR3 position, float pitch, float yaw, float roll)
{
	assert(gfxIndex>=0);

	//pitch rotation
	D3DXMATRIX pitchMatrix;
	D3DXMatrixRotationX(&pitchMatrix, pitch);
	//yaw rotation
	D3DXMATRIX yawMatrix;
	D3DXMatrixRotationY(&yawMatrix, yaw);
	//roll rotation
	D3DXMATRIX rollMatrix;
	D3DXMatrixRotationZ(&rollMatrix, roll);

	D3DXMATRIX translationMatrix;
	D3DXMatrixTranslation(&translationMatrix,position.x, position.y, position.z);

	//fill the world matrix
	D3DXMATRIX savedWorldMatrix;
	D3DXMATRIX worldMatrix;
	D3DXMatrixIdentity(&worldMatrix);
	worldMatrix=(pitchMatrix*yawMatrix*rollMatrix)*translationMatrix;
	//worldMatrix=translationMatrix*(pitchMatrix*yawMatrix*rollMatrix);
	
	//save and set the world matrix
	m_d3dDevice->GetTransform(D3DTS_WORLD, &savedWorldMatrix);
	m_d3dDevice->SetTransform( D3DTS_WORLD, &worldMatrix );
	
	//display the entity and reset the world matrix
	m_vectorGfxEntity[gfxIndex]->doRender();
	m_d3dDevice->SetTransform(D3DTS_WORLD, &savedWorldMatrix);

}

void CVisualisation::displaySkyBox(int gfxIndex)
{
	assert(gfxIndex>=0);

	m_vectorGfxEntity[gfxIndex]->doRender();
}

void CVisualisation::displayTerrain(int gfxIndex)
{
	assert(gfxIndex>=0);

	m_vectorGfxEntity[gfxIndex]->doRender();
}

void CVisualisation::calculCamera(D3DXVECTOR3 position, float pitch, float yaw, float roll)
{
	m_camera->calculateFPSViewMatrix(position, pitch, yaw, roll);
}

void CVisualisation::displayText(LPRECT position, D3DCOLOR color, string text)
{
	m_font->DrawText(0, text.c_str(), -1, position, 0, color);
}

void CVisualisation::displayTexture(int indexTexture, D3DXVECTOR3 position)
{
	assert(indexTexture>=0);

	m_sprite->Begin(D3DXSPRITE_ALPHABLEND);
	m_sprite->Draw(m_vectorTexture[indexTexture], 0, 0, &position, 0xFFFFFFFF);
	m_sprite->End();
}